/**
 * 纯真IP库
 * 工具使用：
 * 1、设置 iptable.dat 文件路径(initIptable)，文件位置: resources/config/iptable.dat
 * 2、调用静态方法进行查询IP
 */
package ms.core.tool;

import java.io.IOException;
import java.io.RandomAccessFile;

public class IpTableTool {
	private String ipFileName = "/config/iptable.dat";		// 默认纯真IP库文件
	private RandomAccessFile ipDataFile;					// 纯真IP数据文件对象
	private final int MODE_1 = 0x01; 						// 重定向国家记录，地区记录
	private final int MODE_2 = 0x02; 						// 重定向国家记录，有地区记录
	private long indexBegin;
	private long indexEnd;

	/**
	 * 初始化IP库查询对象
	 * @param ipTableFile
	 */
	public static IpTableTool getInstance(String ipTableFile) {
		return new IpTableTool(ipTableFile);
	}
	
	IpTableTool(String ipTableFile) {
		ipFileName = ipTableFile;
		try{
			String fileName = IpTableTool.class.getResource(ipFileName).getFile();
			ipDataFile = new RandomAccessFile(fileName, "r");
			indexBegin = readLong(4, 0);
			indexEnd = readLong(4, 4);
		}catch(Exception e){
			Log4j2Tool.error(IpTableTool.class, "initIptable:", e);
		}
	}

	/**
	 * 释放IP库查询对象
	 */	
	public void freeIptable(){
		try{
			if (ipDataFile!=null) ipDataFile.close();
		} catch (IOException e) {
			Log4j2Tool.error(IpTableTool.class, "freeIptable:", e);
		}
		ipDataFile = null;
	}

	//采用二分法查询
	public IpEntry search(String ipStr){
		IpEntry ret = new IpEntry();
		ret.setArea("未知地区");
		ret.setIsp("未知提供商");
		ret.setIp(ipStr);

		long recordCount = (indexEnd - indexBegin) / 7 + 1;
		long itemStart = 0;
		long itemEnd = recordCount - 1;
		long ip = stringIP2Long(ipStr);
		if (ip<0){
			ret.setArea("IP地址错误");
			return ret;
		}

		long middle = 0;
		long midIP = 0;
		while(itemStart <= itemEnd){
			middle = (itemStart +  itemEnd) / 2;
			midIP = readLong(4, indexBegin + middle * 7); 
			if(midIP == ip){
				break;
			}
			else if(midIP < ip){
				itemStart = middle + 1; 
			}
			else{
				itemEnd = middle - 1; 
			}
		}

		//若无完全匹配结果，则向前匹配 
		if((ip < midIP) && middle > 0){
			middle--; 
		}
		long item = readLong(3, indexBegin + middle * 7 + 4);
		String[] result = getInfo(item + 4);//取出信息 
		ret.setArea(result[0]);
		ret.setIsp(result[1]);
		ret.setSegIp(long2StringIP(readLong(4, indexBegin + middle * 7)));
		
		return ret;
	}

	public IpEntry queryIp(String ip){
		IpEntry ret = search(ip);
		return ret;
	}

	//32位整型格式的IP地址(little-endian)转化到字符串格式的IP地址 
	public static String long2StringIP(long ip){
		long ip4 = ip >> 0 & 0x000000FF;
		long ip3 = ip >> 8 & 0x000000FF;
		long ip2 = ip >> 16 & 0x000000FF;
		long ip1 = ip >> 24 & 0x000000FF;
		return String.valueOf(ip1) + "." + String.valueOf(ip2) + "." + String.valueOf(ip3) + "." + String.valueOf(ip4); 
	}

   //字符串格式的IP地址转化到32位整型格式的IP地址(little-endian)
	public static Long stringIP2Long(String ipStr){
		String[] list = ipStr.split("\\.");
		if(list.length != 4){
			System.out.println("IP地址格式错误");
			return -1L;
		}
    
		long ip = Long.parseLong(list[0]) << 24 & 0xFF000000;
		ip += Long.parseLong(list[1]) << 16 & 0x00FF0000; 
		ip += Long.parseLong(list[2]) << 8 & 0x0000FF00;
		ip += Long.parseLong(list[3]) << 0 & 0x000000FF;
		return ip;
	}

	//读取一个n位的
	private long readLong(int nByte, long offset){
		try{
			ipDataFile.seek(offset);
			long result = 0;
			if(nByte > 4 || nByte < 0){
				System.out.println("nBit should be 0-4");
				return -1;
			}
			for(int i = 0; i < nByte; i++){ 
				result |= ((long)ipDataFile.readByte() << 8 * i) & (0xFFL << 8 * i);
			}
			return result;
		}catch(Exception e){
			Log4j2Tool.error(IpTableTool.class, "readLong:", e);
		}
		return -1;
	}

	private String[] getInfo(long itemStartPos){
		String[] result = new String[]{"未知国家","未知地区"};
		try{
			ipDataFile.seek(itemStartPos);
			byte mode = ipDataFile.readByte();
			switch (mode){
				case MODE_1:{ 
					long offset = itemStartPos + 1; 
					long redirPos = readLong(3, offset);
					result = getInfo(redirPos);  
				}
				break;
				case MODE_2:{
					long offset = itemStartPos + 1;
					long redirPos = readLong(3, offset);
					result = getInfo(redirPos);
					result[1] = getArea(offset + 3);
				}
				break;
				default:{//MODE_3
					long offset = itemStartPos;
					int countryLen = getStrLength(offset);
					result[0] = getString(offset, countryLen);
					offset = itemStartPos + countryLen + 1;
					result[1] = getArea(offset);
				}
				break;
			}
			return result;
		} catch(Exception e){
			Log4j2Tool.error(IpTableTool.class, "getInfo:", e);
		}
		return result;
	}

	private String getArea(long offset){
		try{
			ipDataFile.seek(offset);
			byte cityMode = ipDataFile.readByte();
			if(cityMode ==  MODE_2 || cityMode ==  MODE_1){
				offset = readLong(3, offset + 1);
			}
			int cityLen = getStrLength(offset);
			return getString(offset, cityLen);
		}catch(Exception e){
			Log4j2Tool.error(IpTableTool.class, "getArea:", e);
		}
		return "unknow area";
	}

	private int getStrLength(long pos){
		try{
			ipDataFile.seek(pos);
			long strEnd = pos - 1;
			while(ipDataFile.readByte() != (byte)0){
				strEnd++;
			}
			return (int) (strEnd - pos + 1);
		}catch(Exception e){
			Log4j2Tool.error(IpTableTool.class, "getStrLength:", e);
		}
		return -1;
	}

	private String getString(long pos, int len){
		byte buf[] = new byte[len];
		try {
			ipDataFile.seek(pos);
			ipDataFile.read(buf);
			String s = new String(buf, "gbk");
			return s;
		} catch (IOException e) {
			Log4j2Tool.error(IpTableTool.class, "getString:", e);
		}
		return null;
	}

	public static class IpEntry {
		private String ip;
		private String segIp;
		private String area;
		private String isp;
		
		public String getIp() { return ip; }
		public void setIp(String ip) { this.ip = ip; }

		public String getSegIp() { return segIp; }
		public void setSegIp(String segIp) { this.segIp = segIp; }

		public String getArea() { return area; }
		public void setArea(String area) { this.area = area; }
		
		public String getIsp() { return isp; }
		public void setIsp(String isp) { this.isp = isp; }
		
		public String toString() {
			return String.format("%s=>聚合IP:%s,IP归属地:%s,IP提供商:%s", ip, segIp, area, isp);
		}
	}

	public static void main(String[] args) {
		IpTableTool tool = IpTableTool.getInstance("/config/iptable.dat");
		IpEntry entry = tool.queryIp("47.94.94.39");
		System.out.println(entry.toString());
		tool.freeIptable();
	}
}